#############################################################################
# ldms_auto_gateway.pl                                                      #
# (c) 2009 Jack Coates, jack@monkeynoodle.org                               #
# Loosely based on concepts from auto_gateway.vbs                           #
# Released under GPL, see http://www.gnu.org/licenses/gpl.html for license  #
# Patches welcome at the above email address, current version available at  #
# http://www.droppedpackets.org                                             #
#############################################################################
#
# Read configuration, should I ping or download or try both?
# Test reachability to the core.
# If you can reach it, use direct mode.
# If you can't reach it, use gateway mode.
# Jack Coates, jack@monkeynoodle.org

# %PROGRAMFILES%/LANDesk/Shared Files/cbaroot/broker/brokerconf.xml.
# The Order key is the one that needs changing
# 1 == "Connect using the Management Gateway"
# 2 == "Connect directly to LDMS core"
# 0 == "Dynamically determine connection route"

package ldms_auto_gateway;
#############################################################################
# Pragmas and Modules                                                       #
#############################################################################
use strict;
use warnings;
use Env;
use Getopt::Long;
use Win32::TieRegistry (
    Delimiter   => "/",
    ArrayValues => 1,
    SplitMultis => 1,
    AllowLoad   => 1,
    DWordsToHex => 1, 
    qw(REG_SZ REG_DWORD)
);
use Net::Ping;
use Win32::Service;
use Win32::GUI();

#############################################################################
# Variables                                                                 #
#############################################################################
my ( $DEBUG, $ping, $file, $gateway, $help ) = (0,0,0,0,0);
Getopt::Long::Configure( "long_prefix_pattern=(--|\/)",
    "prefix_pattern=(--|-|\/)" );
GetOptions(
    'debug'   => \$DEBUG,
    'ping'    => \$ping,
    'file'    => \$file,
    'gateway' => \$gateway,
    'help'    => \$help,
);

( my $prog = $0 ) =~ s/^.*[\\\/]//x;
my $VERSION = "1.1.1";

my $usage = <<"EOD";

Usage: $prog [/p] [/f] [/d] [/h]
    /p(ing)     ping the core to check for presence
    /f(ile)     download a file from the core to check for presence
    /g(ateway)  force gateway mode regardless of core presence
    /d(ebug)    debug
    /h(elp)     this display

$prog v $VERSION
Jack Coates, jack\@monkeynoodle.org, released under GPL
This program will try to reach the LANDesk core and then configure settings
appropriately. It assumes that the agent has been properly configured to 
work with a LANDesk Management Gateway. The latest version lives at 
http://www.droppedpackets.org/management-gateway-and-remote-control

EOD

if ($help) { &LogDie($usage); }

if (!$gateway) {

    # Using ping and file to detect the core is not supported; you've got to pick
    # one or the other method for core finding.
    if ( $ping  && $file )  { &LogDie( "Make up your mind:\n" . $usage ) }
    if ( !$ping && !$file ) { &LogDie( "You've got to choose one:\n" . $usage ) }
}

# Suppress DOS Windows
BEGIN {
    Win32::SetChildShowWindow(0) if defined &Win32::SetChildShowWindow;
}

# Files and locations
my ( $logfile, $brokerfile, $brokercrt, $localsch, $ldiscn32, $miniscan );
if ( !defined($PROGRAMFILES) ) { $PROGRAMFILES = "C:\\Program\ Files"; }
$PROGRAMFILES = Win32::GetShortPathName($PROGRAMFILES);
$logfile      = $PROGRAMFILES . "\\LANDesk\\LDClient\\ldms_auto_gateway.log";
$brokerfile =
  $PROGRAMFILES . "\\LANDesk\\Shared\ Files\\cbaroot\\broker\\broker.conf.xml";
$brokercrt =
  $PROGRAMFILES . "\\LANDesk\\Shared\ Files\\cbaroot\\broker\\broker.crt";
$localsch = $ldiscn32 = $miniscan = $PROGRAMFILES . "\\LANDesk\\LDClient";
$localsch .= "\\localsch.exe";
$ldiscn32 .= "\\ldiscn32.exe";
$miniscan .= "\\miniscan.exe";

# Global variables I'll need
my (
    $core,             $present,        $rc_gateway,
    $LOG,              $broker_gateway, $brokerdata,
    $change_semaphore, $RegKey,         $exitmessage
);

if ($DEBUG) {

    # Prepare logging system
    open( $LOG, '>', $logfile ) or die "Cannot open $logfile - $!";
    print $LOG localtime() . " $prog $VERSION starting.\n";
    close($LOG);
}

#############################################################################
# Main Loop                                                                 #
#############################################################################

# Is this thing even able to use a gateway?
if ( !-e $brokercrt ) {
    $exitmessage = "Not configured to use the Management Gateway.";
    &Log( $exitmessage . " $brokercrt doesn't exist." );
    exit 1;
}

# If we're doing gateway mode, there's no need to find the core at all
if ($gateway) {
    $present = 0;
} else {
    # Can I recognize how to find the core?
    $core = &NameMyCore;
    if ( !$core ) {
        $exitmessage = "Cannot locate core name in registry.";
        &LogDie($exitmessage);
    }

    # Is the core present?
    $present = &FindMyCore;

}

# What's the current gateway mode?
$rc_gateway = &ReadGatewayMode;

# What's the current brokerconf mode?
# Note that this won't do anything if brokerconf.xml is in Dynamic mode,
# because it would return 0.
$broker_gateway = &ReadBrokerConf;

if ($present) {

    &Log("$core contacted, this is home net and we should use direct mode.");

    # Configure the ldproxy behavior
    if ( -e $brokerfile && $broker_gateway != 2 ) {

        &Log( "Broker file exists and is not direct or dynamic. " );
        &WriteBrokerConf(2);
        $change_semaphore = 1;
    }

    # Set up localscheduler to do the right thing about inventory
    &SetLocalSch("direct");

    # Configure the Remote Control Agent
    if ( $rc_gateway == 1 ) {

        &Log( "Gateway mode is enabled, so turn it off." );
        &WriteGatewayMode("0");
        &RestartService("ISSUSER");
        $change_semaphore = 1;
    }

}
else {

    &Log(
"$core not contacted, this is foreign net and we should use the gateway."
    );

    # Configure the ldproxy behavior
    if ( -e $brokerfile && $broker_gateway != 1 ) {

        &Log( "Broker file exists and is not direct or dynamic. " );
        &WriteBrokerConf(1);
        $change_semaphore = 1;
    }

    # Configure the Remote Control Agent
    if ( $rc_gateway == 0 ) {

        &Log( "Direct mode is enabled, so turn it off." );
        &WriteGatewayMode("1");
        &RestartService("ISSUSER");
        $change_semaphore = 1;
    }

    # Set up localscheduler to do the right thing about inventory
    &SetLocalSch("gateway");
}

# Let's make sure that the service is really running
if (&ServiceCheck("ISSUSER") == 1) { &RestartService("ISSUSER"); }

# If I had to make changes, that's a good indication that inventory needs to
# be refreshed at the core. If we're coming from direct to gateway, miniscan
# won't work and a regular scan should be sent. If we're coming from gateway
# to direct, a miniscan would work, but a a regular scan will probably
# initiate the out-of-sync process (which I might as well start now). So, if
# there was a change, I'm going to initiate a regular inventory scan.
if ($change_semaphore) {
    my $scancommand = "\"$ldiscn32\" /NTT=$core\:5007 /S=$core "
      . "/I=HTTP://$core/ldlogon/ldappl3.ldz /NOUI /NOCD /F-";
    system( 1, "$scancommand" );
}

# all done
exit 0;

#############################################################################
# Subroutines                                                               #
#############################################################################

### LogDie handler #########################################################
sub LogDie {
    my $msg = shift;
    if ($DEBUG) {
        if ( !defined($msg) ) { $msg = "LogDie: Can't report nothing"; }
        open( $LOG, '>>', "$logfile" ) or die "Can't open $logfile - $!";
        $LOG->autoflush();
        print $LOG localtime() . ": $msg\n";
        close($LOG);
    }
    Win32::GUI::MessageBox(0,"$msg","ldms_auto_gateway",48);    
    exit 1;
}

### Log handler #############################################################
sub Log {
    if ($DEBUG) {
        my $msg = shift;
        if ( !defined($msg) ) { $msg = "Log: Can't report nothing"; }
        open( $LOG, '>>', "$logfile" ) or die "Can't open $logfile - $!";
        $LOG->autoflush();
        print $LOG localtime() . ": $msg\n";
        close($LOG);
    }
    return 0;
}
### End of Log handler ######################################################

### overwrite the current LocalSched inventory scan type ####################
sub SetLocalSch {
    my $input = shift;

    # If any of these files can't be found, something ain't right
    if ( !-e $localsch || !-e $ldiscn32 || !-e $miniscan ) {
        &Log(   "missing critical files: \n"
              . "\tLocal Scheduler: $localsch\n"
              . "\tInventory Scanner: $ldiscn32\n"
              . "\tMini Scanner: $miniscan" );
        return 1;
    }
    my $result;
    $result = system("\"$localsch\" /del /taskid=778");
    if ( $input eq "gateway" ) {
        my $schedcommand =
            "\"$localsch\" /exe='$ldiscn32' "
          . "/cmd='/NTT=$core\:5007 /S=$core "
          . "/I=HTTP://$core/ldlogon/ldappl3.ldz /NOUI' /ipaddr /taskid=778";
        $result = system("$schedcommand");
        &Log("set local scheduler taskid 778 to use $ldiscn32");
    }
    if ( $input eq "direct" ) {
        my $schedcommand =
          "\"$localsch\" /exe='$miniscan' /ipaddr " . " /taskid=778";
        $result = system("$schedcommand");
        &Log("set local scheduler taskid 778 to use $miniscan");
    }
    return 0;
}
### End of SetLocalSChed ##################################################

### ASCII to Integer subroutine ###############################################
sub AtoI {
    my $t = 0;
    foreach my $d ( split( //, shift() ) ) {
        $t = $t * 10 + $d;
    }
    return $t;
}

### What's the current Brokerconfmode? #####################################
sub ReadBrokerConf {

    # The file might not exist, but it does have an effect if it's there, so
    # it needs to be tested for before doing anything with it.
    if ( !-e $brokerfile ) { return 0; }
    my ( $output, $BH, $line );
    open( $BH, '<', "$brokerfile" ) or die "Can't open $brokerfile - $!";
    $brokerdata = <$BH>;
    close($BH);
    $line = $brokerdata;
    $line =~ /\<order\>(\d)/ix;

    if ($1) {
        $output = &AtoI($1);
    }
    else {
        $output = 0;
    }
    if ( $DEBUG && $1 ) {
        my $brokermode =
            $output == 1 ? 'Connect using the Management Gateway'
          : $output == 2 ? 'Connect directly to LDMS core'
          : $output == 0 ? 'Dynamically determine connection route'
          :                $output;
        &Log("$brokerfile is forcing mode $output\: $brokermode");
    }

    &Log("If it's using dynamic mode, override it with a specific invalid mode instead.");
    if ( $output == 0 ) { $output = 3; }
    return $output;
}
### End of ReadBrokerConf ##################################################

### Set a new Brokerconf mode ##############################################
sub WriteBrokerConf {

    # The file might not exist, but it does have an effect if it's there, so
    # it needs to be tested for before doing anything with it.
    if ( !-e $brokerfile ) { return 0; }
    my $input = shift;
    my ( $BH, $output );
    $brokerdata =~ s/\<order\>\d/\<order\>$input/gix;
    open( $BH, '>', "$brokerfile" ) or die "Can't open $brokerfile - $!";
    print $BH $brokerdata;
    close($BH);
    &Log("Wrote mode $input into $brokerfile");
    return 0;
}
### End of WriteBrokerConf #################################################

### What's the current Gateway mode? #######################################
sub ReadGatewayMode {
    my ($output);

    # Read the current mode from the registry
    my $wuserkey="HKEY_LOCAL_MACHINE/Software/Intel/LANDesk/WUSER32";
    $RegKey = $Registry->{$wuserkey};
    if ($RegKey) {
        $output = $RegKey->GetValue("Gateway");
    } else {
        &Log("Can't read from $wuserkey");
    }
    if ($output) {
        &Log("Found $output in Gateway registry key for Remote Control");
        $output = hex($output);
        &Log("Decoded hex to $output");
    } else {
        &Log("Can't read from $wuserkey Gateway");
    }
    return $output;

}
### End of ReadGatewayMode ##################################################

### Set a new Gateway mode ##################################################
sub WriteGatewayMode {

    my $input = shift;

    # Write discovered data
    $RegKey = $Registry->{"HKEY_LOCAL_MACHINE/Software/Intel/LANDesk/WUSER32"};
    if ($RegKey) {
        $RegKey->SetValue( "Gateway", pack( "L", $input ), "REG_DWORD" );
    }
    &Log("Wrote $input in Gateway registry key for Remote Control");
    return 0;
}
### End of WriteGatewayMode #################################################

### Find my core ############################################################
sub NameMyCore {
    my ($output);

    # Tie the registry to find CoreServer
    $RegKey = $Registry->{"HKEY_LOCAL_MACHINE/Software/Intel/LANDesk/LDWM"};
    if ($RegKey) {
        $output = $RegKey->GetValue("CoreServer");
    }
    &Log("Found $output in registry for core name.");
    return $output;

}
### End of Name that core ###################################################

### Find my core ############################################################
sub FindMyCore {

    my $output = 0;

    # If the method is ping, try to ping the server
    if ($ping) {
        my $p = Net::Ping->new( "icmp", 1 );
        my $didping = $p->ping($core);
        if ($didping) {
            $output = 1;
            &Log("Successfully pinged $core.");
        }
        $p->close();
    }

    # If the method is file try to read a file directly from the server
    if ($file) {
        my $testfile = "\\\\$core\\ldlogon\\ldiscn.vroot";
        if ( -e $testfile ) {
            $output = 1;
            &Log("Successfully opened $testfile.");
        }
    }
    return $output;
}
### End of Find That Core ###################################################

### Service restart subroutine ##############################################
sub RestartService {

    my $target = shift;
    &Log("Stopping $target service.");
    Win32::Service::StopService( '', $target )
      or &Log("RestartService: Having some trouble with $target");

    sleep 8;
    &Log("Starting $target service.");
    my $retval = Win32::Service::StartService( '', $target );
    if ($retval) {
        &Log("$target service restarted successfully.");
    }
    return 0;
}
### End of Service restart subroutine #######################################

### Service check subroutine ################################################
sub ServiceCheck {

    my $servicetarget = shift;
    if ( !defined($servicetarget) ) {
        &LogWarn( "ServiceCheck called without a service to check. "
              . "$servicetarget" );
        return 1;
    }    

    my %status;
    Win32::Service::GetStatus( '', $servicetarget, \%status );
    if ( $status{CurrentState} == 1 ) {
        &Log( "$servicetarget service down" );
    }
    return $status{CurrentState};
}
### End of Service check subroutine ##########################################

1;
__END__

